#include <ani.h>
#include <array>
#include <iostream>
#include <vector>

std::string ANIUtils_ANIStringToStdString(ani_env *env,ani_string ani_str){
    ani_size  strSize;
    env->String_GetUTF8Size(ani_str, &strSize);
   
    std::vector<char> buffer(strSize + 1); // +1 for null terminator
    char* utf8_buffer = buffer.data();

    //String_GetUTF8 Supportted by https://gitee.com/openharmony/arkcompiler_runtime_core/pulls/3416
    ani_size bytes_written = 0;
    env->String_GetUTF8(ani_str, utf8_buffer, strSize + 1, &bytes_written);
    
    utf8_buffer[bytes_written] = '\0'; 
    std::string content = std::string(utf8_buffer); 
    return content;
}

static void processArray([[maybe_unused]] ani_env *env, ani_array_ref stringArray)
{
    ani_size size;
    if(ANI_OK != env->Array_GetLength(stringArray, &size)){
        std::cerr << "Array_GetLength FAILED" << std::endl;
        return;
    }
    std::vector<std::string> strings;
    for(ani_size i = 0; i < size ; i++){
        ani_ref string_ref;
        auto status = env->Array_Get_Ref(stringArray, i, &string_ref);
        if(status != ANI_OK){
            std::cerr << "Array_Get_Ref FAILED index: " << i << std::endl;
            return;
        }
        strings.emplace_back(ANIUtils_ANIStringToStdString(env, static_cast<ani_string>(string_ref)));
    }
    for (const auto &s : strings) {
        std::cout << "Array String Content:" << s.c_str() << std::endl;
     }
}

static ani_ref newArray([[maybe_unused]] ani_env *env){
    ani_class stringCls = nullptr;
    if (ANI_OK != env->FindClass("std.core.String", &stringCls)){
        std::cerr << "FindClass std.core.String Failed" << std::endl;
    }

    ani_ref undefinedRef = nullptr;
    if(ANI_OK !=env->GetUndefined(&undefinedRef)){
        std::cerr << "GetUndefined Failed" << std::endl;
    }
    
    ani_array_ref array;
    std::array<std::string,5> strings = {
        "Sky", 
        "King",
        "Conver",
        "Ground",
        "Tiger",
    };


    if(ANI_OK != env->Array_New_Ref(stringCls, strings.size(), undefinedRef, &array )){
        std::cerr << "Array_New_Ref Failed" << std::endl;
    }

    ani_size index = 0;
    for(auto string:strings){
        ani_string ani_str;
        if(ANI_OK != env->String_NewUTF8(string.c_str(), string.size(), &ani_str)){
            break;
        }
        if(ANI_OK != env->Array_Set_Ref(array, index, ani_str)){
            break;
        }   
        index ++;
    } 
    return array;
}


static ani_ref newArrayClass([[maybe_unused]] ani_env *env){
   
    ani_class arrayCls = nullptr;
    if (ANI_OK != env->FindClass("escompat.Array", &arrayCls)){
        std::cerr << "FindClass escompat.Array Failed" << std::endl;
    }

    ani_method arrayCtor;
    if(ANI_OK != env->Class_FindMethod(arrayCls, "<ctor>", "i:", &arrayCtor)){
        std::cerr << "Class_FindMethod <ctor> Failed" << std::endl;
    }

       
    std::array<std::string,5> strings = {
        "Sky", 
        "King",
        "Conver",
        "Ground",
        "Tiger",
    };

    ani_object arrayObj;
    if(ANI_OK != env->Object_New(arrayCls, arrayCtor, &arrayObj, strings.size())){
        std::cerr << "Object_New Array Faild" << std::endl;
        return arrayObj;
    }
    ani_size index = 0;
    for(auto string:strings){
        std::cout << string << std::endl;
        ani_string ani_str;
        if(ANI_OK != env->String_NewUTF8(string.c_str(), string.size(), &ani_str)){
            std::cerr << "String_NewUTF8 Faild " << std::endl;
            break;
        }
       if(ANI_OK != env->Object_CallMethodByName_Void(arrayObj, "$_set", "iC{std.core.Object}:", index, ani_str)){
           std::cerr << "Object_CallMethodByName_Void  $_set Faild " << std::endl;
            break;
       }   
       index ++;
    } 
    return arrayObj;
}


static void processArrayClass([[maybe_unused]] ani_env *env, ani_object arrayObj)
{
    ani_double length;
    
    if(ANI_OK != env->Object_GetPropertyByName_Double(arrayObj, "length", &length)){
        std::cerr << "Object_GetPropertyByName_Double length Failed" << std::endl;    
        return;
    }

    std::vector<std::string> strings;
    for(int i = 0; i < int(length); i++){
        ani_ref stringEntryRef;
        if(ANI_OK != env->Object_CallMethodByName_Ref(arrayObj, "$_get", "i:C{std.core.Object}", &stringEntryRef, (ani_int)i)){
            std::cerr << "Object_GetPropertyByName_Double length Failed" << std::endl;    
            return;
        } 
        strings.emplace_back(ANIUtils_ANIStringToStdString(env, static_cast<ani_string>(stringEntryRef)));
    }
    for (const auto &s : strings) {
        std::cout << "Array String Content:" << s.c_str() << std::endl;
     }
}

ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result)
{
    ani_env *env;
    if (ANI_OK != vm->GetEnv(ANI_VERSION_1, &env)) {
        std::cerr << "Unsupported ANI_VERSION_1" << std::endl;
        return ANI_ERROR;
    }

    static const char *moduleName = "Lani_array;";
    ani_module module;
    if (ANI_OK != env->FindModule(moduleName, &module)) {
        std::cerr << "Not found '" << moduleName << "'" << std::endl;
        return ANI_ERROR;
    }

    std::array methods = {
        ani_native_function {"processArray", nullptr, reinterpret_cast<void *>(processArray)},
        ani_native_function {"processArrayClass", nullptr, reinterpret_cast<void *>(processArrayClass)},
        ani_native_function {"newArray", ":[Lstd/core/String;", reinterpret_cast<void *>(newArray)},
        ani_native_function {"newArrayClass", ":Lescompat/Array;", reinterpret_cast<void *>(newArrayClass)},
    };
    
    std::cout << "Start bind native methods to '" << moduleName << "'" << std::endl;

    if (ANI_OK != env->Module_BindNativeFunctions(module, methods.data(), methods.size())) {
        std::cerr << "Cannot bind native methods to '" << moduleName << "'" << std::endl;
        return ANI_ERROR;
    };
    std::cout << "Finish bind native methods to '" << moduleName << "'" << std::endl;

    *result = ANI_VERSION_1;
    return ANI_OK;
}